package org.zero.jose4j;

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.ErrorCodes;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.HmacKey;
import org.jose4j.lang.JoseException;

import java.util.Map;

/**
 * @author Zero (cnzeropro@qq.com)
 * @date 2021/11/24 10:42
 */
@UtilityClass
@Slf4j
public class Jose4jUtil {
    /**
     * 密钥（最少32个字符）
     */
    private final String SECRET_KEY = "d2a819dfa0154a5e97064b43f9bd4833";

    /**
     * 签名算法
     */
    private final String ALGORITHM = AlgorithmIdentifiers.HMAC_SHA256;

    /**
     * Token 签发者
     */
    private final String ISSUER = "Zero";

    /**
     * 默认过期时间，单位分钟
     */
    private final int EXPIRATION = 24 * 60;

    public String createToken(Map<String, Object> map) throws JoseException {
        JsonWebSignature jsonWebSignature = new JsonWebSignature();
        // JWT 使用 RSA 私钥签名
        jsonWebSignature.setKey(new HmacKey(SECRET_KEY.getBytes()));
        // 在 JWT / JWS 上设置签名算法
        jsonWebSignature.setAlgorithmHeaderValue(ALGORITHM);
        // 为 JsonWebSignature 对象添加负载：JwtClaims 对象的 Json 内容
        jsonWebSignature.setPayload(jwtClaims(map).toJson());

        return jsonWebSignature.getCompactSerialization();
    }

    public boolean verify(String token) {
        try {
            JwtConsumer jwtConsumer = jwtConsumer();
            jwtConsumer.processToClaims(token);
            return true;
        } catch (Exception e) {
            handleException(e);
            return false;
        }
    }

    private JwtClaims jwtClaims(Map<String, Object> map) {
        JwtClaims claims = new JwtClaims();

        // 设置 Token 的签发者
        claims.setIssuer(ISSUER);

        // 设置过期时间
        //  claims.setExpirationTime(NumericDatite.fromSeconds());
        claims.setExpirationTimeMinutesInTheFuture(EXPIRATION);

        // 设置主题
        claims.setSubject("Subject");

        // 设置 Token 受众（接收者）
        claims.setAudience("Audience X", "Audience Y", "Audience Z");

        // 设置生效时间
        // claims.setNotBefore();
        claims.setNotBeforeMinutesInThePast(2);

        // 设置【Token 发布/创建】时间
        // claims.setIssuedAt();
        claims.setIssuedAtToNow();

        // 设置 JWT 唯一 ID
        // claims.setJwtId();
        claims.setGeneratedJwtId();

        // 设置信息
        map.forEach(claims::setClaim);

        return claims;
    }

    private JwtConsumer jwtConsumer() {
        return new JwtConsumerBuilder()
                // 在验证时间时留出一些余量以解决时钟偏差问题
                .setAllowedClockSkewInSeconds(30)
                // 设置解密密钥
                // .setDecryptionKey()
                // 设置解密密钥解析器
                // .setDecryptionKeyResolver()
                // .setDisableRequireSignature()
                // 必须设置过期时间
                .setRequireExpirationTime()
                // 必须设置 Subject
                .setRequireSubject()
                // 必须设置 Token 签发者
                .setExpectedIssuer(ISSUER)
                // 必须设置 Token 签发给谁
                .setExpectedAudience("Audience X")
                // 设置用于验证签名的公钥
                .setVerificationKey(new HmacKey(SECRET_KEY.getBytes()))
                // 设置允许的预期签名算法
                .setJwsAlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, ALGORITHM)
                .build();
    }

    /**
     * 处理校验 JWT 并将其处理成 JwtClaims 对象过程中出现的异常
     */
    private void handleException(Exception e) {
        if (e instanceof InvalidJwtException) {
            InvalidJwtException ite = (InvalidJwtException) e;
            try {
                JwtClaims jwtClaims = ite.getJwtContext().getJwtClaims();
                // 异常是否因 JWT 过期触发
                if (ite.hasExpired()) {
                    log.warn("Expired at {}", jwtClaims.getExpirationTime());
                    return;
                }
                // 异常是否因 Audience 无效触发
                if (ite.hasErrorCode(ErrorCodes.AUDIENCE_INVALID)) {
                    log.warn("Invalid audience: {}", jwtClaims.getAudience());
                    return;
                }
                // 异常是否因缺少 Audience 触发
                if (ite.hasErrorCode(ErrorCodes.AUDIENCE_MISSING)) {
                    log.warn("Audience missing!");
                    return;
                }
                // 异常是否因缺少加密触发
                if (ite.hasErrorCode(ErrorCodes.ENCRYPTION_MISSING)) {
                    log.warn("Encryption missing!");
                    return;
                }
                // 异常是否因缺少过期时间触发
                if (ite.hasErrorCode(ErrorCodes.EXPIRATION_MISSING)) {
                    log.warn("Expiration missing!");
                    return;
                }
                // 异常是否因过期时间太长触发
                if (ite.hasErrorCode(ErrorCodes.EXPIRATION_TOO_FAR_IN_FUTURE)) {
                    log.warn("Expiration too far in future: {}", jwtClaims.getExpirationTime());
                    return;
                }
                // 异常是否因缺乏完整性触发
                if (ite.hasErrorCode(ErrorCodes.INTEGRITY_MISSING)) {
                    log.warn("Integrity missing!");
                    return;
                }
                // 异常是否因发布时间无效触发
                if (ite.hasErrorCode(ErrorCodes.ISSUED_AT_INVALID_FUTURE)) {
                    log.warn("Issued at invalid future: {}", jwtClaims.getIssuedAt());
                    return;
                }
                // 异常是否因发布时间无效触发
                if (ite.hasErrorCode(ErrorCodes.ISSUED_AT_INVALID_PAST)) {
                    log.warn("Issued at invalid past: {}", jwtClaims.getIssuedAt());
                    return;
                }
                // 异常是否因缺少发布时间触发
                if (ite.hasErrorCode(ErrorCodes.ISSUED_AT_MISSING)) {
                    log.warn("Issued at missing!");
                    return;
                }
                // 异常是否因签发者无效触发
                if (ite.hasErrorCode(ErrorCodes.ISSUER_INVALID)) {
                    log.warn("Issuer invalid: {}", jwtClaims.getIssuer());
                    return;
                }
                // 异常是否因缺少签发者触发
                if (ite.hasErrorCode(ErrorCodes.ISSUER_MISSING)) {
                    log.warn("Issuer missing!");
                    return;
                }
                // 异常是否因 JSON 无效触发
                if (ite.hasErrorCode(ErrorCodes.JSON_INVALID)) {
                    log.warn("Json invalid: {}", jwtClaims.getRawJson());
                    return;
                }
                // 异常是否因缺少 JWT ID 触发
                if (ite.hasErrorCode(ErrorCodes.JWT_ID_MISSING)) {
                    log.warn("JWT ID missing!");
                    return;
                }
                // 异常是否因 JwtClaims 格式错误触发
                if (ite.hasErrorCode(ErrorCodes.MALFORMED_CLAIM)) {
                    log.warn("Malformed claim!");
                    return;
                }
                // 异常是否因缺少生效时间触发
                if (ite.hasErrorCode(ErrorCodes.NOT_BEFORE_MISSING)) {
                    log.warn("Not before missing!");
                    return;
                }
                // 异常是否因 Token 尚未生效触发
                if (ite.hasErrorCode(ErrorCodes.NOT_YET_VALID)) {
                    log.warn("Not yet valid: {}", jwtClaims.getNotBefore());
                    return;
                }
                // 异常是否因 Token 的 Signature 部分无效触发
                if (ite.hasErrorCode(ErrorCodes.SIGNATURE_INVALID)) {
                    log.warn("Signature invalid: {}", jwtClaims.toString());
                    return;
                }
                // 异常是否因 Token 的 Signature 部分缺失触发
                if (ite.hasErrorCode(ErrorCodes.SIGNATURE_MISSING)) {
                    log.warn("Signature missing!");
                    return;
                }
                // 异常是否因 Subject 无效触发
                if (ite.hasErrorCode(ErrorCodes.SUBJECT_INVALID)) {
                    log.warn("Subject invalid: {}", jwtClaims.getSubject());
                    return;
                }
                // 异常是否因 Subject 缺失触发
                if (ite.hasErrorCode(ErrorCodes.SUBJECT_MISSING)) {
                    log.warn("Subject missing!");
                    return;
                }
                // 异常是否因 Type 无效触发
                if (ite.hasErrorCode(ErrorCodes.TYPE_INVALID)) {
                    log.warn("Type invalid: {}", jwtClaims.getRawJson());
                    return;
                }
                // 异常是否因 Type 缺失触发
                if (ite.hasErrorCode(ErrorCodes.TYPE_MISSING)) {
                    log.warn("Type missing!");
                }
            } catch (MalformedClaimException mce) {
                log.warn("Malformed claim", mce);
            }
        } else {
            log.warn("Unknown exception", e);
        }
    }
}
